From 1baf43746d7b7b772fbf36381afb90f4c5afc4ba Mon Sep 17 00:00:00 2001 From: Quixos Template Publisher Date: Sat, 1 Jan 2000 00:00:00 +0000 Subject: [PATCH] Export template source from b1a88e33d6933cafbe89781effd1f75cc8fe6272:quixos-instance/packages/web-studio-canvas-runtime --- .gitignore | 8 ++ .quixos-template-source.json | 4 + .yarn/plugins/yarn-plugin-nixify.cjs | 1 + .yarnrc.yml | 24 ++++++ README.md | 43 +++++++++++ components.nix | 6 ++ flake.lock | 108 +++++++++++++++++++++++++++ flake.nix | 55 ++++++++++++++ package.json | 5 ++ package.qx | 45 +++++++++++ queries/canvas-index-inputs.graphql | 1 + queries/canvas-index.graphql | 1 + queries/canvas-inputs.graphql | 1 + queries/canvas-view.graphql | 1 + queries/label-inputs.graphql | 1 + queries/labels.graphql | 1 + queries/placeable-inputs.graphql | 1 + queries/placeable-view.graphql | 1 + queries/placement-inputs.graphql | 1 + queries/search-inputs.graphql | 1 + queries/search.graphql | 1 + queries/title-inputs.graphql | 1 + queries/title-view.graphql | 1 + quixos.check.json | 4 + quixos.lock | 9 +++ quixos.react.json | 19 +++++ src/browser.ts | 3 + src/canvas-card.tsx | 2 + src/portable.ts | 92 +++++++++++++++++++++++ src/server.ts | 7 ++ tsconfig.json | 11 +++ 31 files changed, 459 insertions(+) create mode 100644 .gitignore create mode 100644 .quixos-template-source.json create mode 100644 .yarn/plugins/yarn-plugin-nixify.cjs create mode 100644 .yarnrc.yml create mode 100644 README.md create mode 100644 components.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 package.json create mode 100644 package.qx create mode 100644 queries/canvas-index-inputs.graphql create mode 100644 queries/canvas-index.graphql create mode 100644 queries/canvas-inputs.graphql create mode 100644 queries/canvas-view.graphql create mode 100644 queries/label-inputs.graphql create mode 100644 queries/labels.graphql create mode 100644 queries/placeable-inputs.graphql create mode 100644 queries/placeable-view.graphql create mode 100644 queries/placement-inputs.graphql create mode 100644 queries/search-inputs.graphql create mode 100644 queries/search.graphql create mode 100644 queries/title-inputs.graphql create mode 100644 queries/title-view.graphql create mode 100644 quixos.check.json create mode 100644 quixos.lock create mode 100644 quixos.react.json create mode 100644 src/browser.ts create mode 100644 src/canvas-card.tsx create mode 100644 src/portable.ts create mode 100644 src/server.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..173585e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +src/gen/ +.quixos/ +result +.yarn/cache/ +.yarn/install-state.gz +.yarn/unplugged/ diff --git a/.quixos-template-source.json b/.quixos-template-source.json new file mode 100644 index 0000000..f5f272d --- /dev/null +++ b/.quixos-template-source.json @@ -0,0 +1,4 @@ +{ + "monorepoCommit": "b1a88e33d6933cafbe89781effd1f75cc8fe6272", + "sourcePath": "quixos-instance/packages/web-studio-canvas-runtime" +} diff --git a/.yarn/plugins/yarn-plugin-nixify.cjs b/.yarn/plugins/yarn-plugin-nixify.cjs new file mode 100644 index 0000000..44f7f79 --- /dev/null +++ b/.yarn/plugins/yarn-plugin-nixify.cjs @@ -0,0 +1 @@ +module.exports={name:"yarn-plugin-nixify",factory:function(e){var t;return(()=>{"use strict";var n={d:(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},r={};n.r(r),n.d(r,{default:()=>E});const a=e("@yarnpkg/core"),o=e("clipanion");class i extends o.Command{constructor(...e){super(...e),this.locator=o.Option.String({required:!1})}async execute(){const e=await a.Configuration.find(this.context.cwd,this.context.plugins),{project:t}=await a.Project.find(e,this.context.cwd),n=await a.Cache.find(e),r=e.makeFetcher();return(await a.StreamReport.start({configuration:e,stdout:this.context.stdout},(async e=>{if(this.locator){const{locatorHash:o}=a.structUtils.parseLocator(this.locator,!0),i=t.originalPackages.get(o);if(!i)return void e.reportError(0,`Invalid locator: ${this.locator}`);await r.fetch(i,{checksums:t.storedChecksums,project:t,cache:n,fetcher:r,report:e})}else await e.startTimerPromise("Resolution step",(async()=>{await t.resolveEverything({report:e,lockfileOnly:!0})})),await e.startTimerPromise("Fetch step",(async()=>{await t.fetchEverything({cache:n,report:e,fetcher:r})}))}))).exitCode()}}i.paths=[["nixify","fetch"]];const s=e("@yarnpkg/fslib"),c=e("crypto");class l extends o.Command{constructor(...e){super(...e),this.locator=o.Option.String(),this.source=o.Option.String(),this.installLocation=o.Option.String()}async execute(){const e=await a.Configuration.find(this.context.cwd,this.context.plugins),{project:t}=await a.Project.find(e,this.context.cwd);return await t.restoreInstallState({restoreResolutions:!1}),(await a.StreamReport.start({configuration:e,stdout:this.context.stdout},(async n=>{await t.resolveEverything({report:n,lockfileOnly:!0});const r=a.structUtils.parseLocator(this.locator,!0),o=t.storedPackages.get(r.locatorHash);if(!o)return void n.reportError(0,`Invalid locator: ${this.locator}`);const i=s.ppath.join(t.cwd,this.installLocation);await s.xfs.mkdirpPromise(s.ppath.dirname(i)),await a.execUtils.execvp("cp",["-R",this.source,i],{cwd:t.cwd,strict:!0}),await a.execUtils.execvp("chmod",["-R","u+w",i],{cwd:t.cwd,strict:!0});const l=(0,c.createHash)("sha512");l.update(process.versions.node),e.triggerHook((e=>e.globalHashGeneration),t,(e=>{l.update("\0"),l.update(e)}));const d=l.digest("hex"),p=new Map,h=e=>{let n=p.get(e.locatorHash);if(void 0!==n)return n;const r=t.storedPackages.get(e.locatorHash);if(void 0===r)throw new Error("Assertion failed: The package should have been registered");const o=(0,c.createHash)("sha512");o.update(e.locatorHash),p.set(e.locatorHash,"");for(const e of r.dependencies.values()){const n=t.storedResolutions.get(e.descriptorHash);if(void 0===n)throw new Error(`Assertion failed: The resolution (${a.structUtils.prettyDescriptor(t.configuration,e)}) should have been registered`);const r=t.storedPackages.get(n);if(void 0===r)throw new Error("Assertion failed: The package should have been registered");o.update(h(r))}return n=o.digest("hex"),p.set(e.locatorHash,n),n},u=(0,c.createHash)("sha512").update(d).update(h(o)).update(i).digest("hex");t.storedBuildState.set(o.locatorHash,u),await t.persistInstallStateFile()}))).exitCode()}}l.paths=[["nixify","inject-build"]];const d=e("@yarnpkg/plugin-pnp"),p=JSON.stringify,h=(e,t,n=!1)=>t.split("\n").map((t=>t||n?e+t:t)).join("\n"),u=(e,t)=>{let n=e;for(const[e,r]of Object.entries(t))if("string"==typeof r&&(n=n.replace(new RegExp(`@@${e}@@`,"g"),r)),"boolean"==typeof r)for(;;){const t=n.split("\n"),a=t.indexOf(`#@@ IF ${e}`),o=t.indexOf(`#@@ ENDIF ${e}`);if(-1===a||o{if(!n)return;const o=s.npath.toPortablePath(this.binDir);for(const[r,a]of n.manifest.bin){const n=s.ppath.join(o,r),i=s.ppath.join(t.cwd,s.npath.toPortablePath(a));await this.writeWrapper(n,i,{configuration:e,project:t})}if(e.get("installNixBinariesForDependencies")){await t.resolveEverything({report:r,lockfileOnly:!0});const n=await a.scriptUtils.getPackageAccessibleBinaries(t.topLevelWorkspace.anchoredLocator,{project:t});for(const[r,[a,i]]of n.entries()){const n=s.ppath.join(o,r);await this.writeWrapper(n,s.npath.toPortablePath(i),{configuration:e,project:t})}}}))).exitCode()}async writeWrapper(e,t,{configuration:n,project:r}){let a;switch(n.get("nodeLinker")){case"pnp":{const e=(0,d.getPnpPath)(r),n=[];await s.xfs.existsPromise(e.cjs)&&n.push(`--require "${s.npath.fromPortablePath(e.cjs)}"`),await s.xfs.existsPromise(e.esmLoader)&&n.push(`--experimental-loader "${(0,f.pathToFileURL)(s.npath.fromPortablePath(e.esmLoader)).href}"`),a=u("#!/bin/sh\nexport NODE_OPTIONS='@@NODE_OPTIONS@@'\nexec '@@NODE_PATH@@' '@@BINARY_PATH@@' \"$@\"\n",{NODE_PATH:process.execPath,NODE_OPTIONS:n.join(" "),BINARY_PATH:t});break}case"node-modules":a=u("#!/bin/sh\nexec '@@NODE_PATH@@' '@@BINARY_PATH@@' \"$@\"\n",{NODE_PATH:process.execPath,BINARY_PATH:t});break;default:throw Error("Assertion failed: Invalid nodeLinker")}await s.xfs.writeFilePromise(e,a),await s.xfs.chmodPromise(e,493)}}g.paths=[["nixify","install-bin"]];const m=e("os"),y=e("@yarnpkg/plugin-patch"),b=(e,t)=>(0,c.createHash)(e).update(t).digest(),x=(e,t,{storePath:n="/nix/store",recursive:r=!1}={})=>{const[a,o]=t.split("-"),i=Buffer.from(o,"base64").toString("hex"),c=b("sha256",`fixed:out:${r?"r:":""}${a}:${i}:`).toString("hex"),l=(e=>{let t="",n=[...e].reverse().map((e=>e.toString(2).padStart(8,"0"))).join("");for(;n;)t+="0123456789abcdfghijklmnpqrsvwxyz"[parseInt(n.slice(0,5),2)],n=n.slice(5);return t})(((e,t)=>{const n=Buffer.alloc(20);for(let t=0;te.replace(/^\.+/,"").replace(/[^a-zA-Z0-9+._?=-]+/g,"-").slice(0,207)||"unknown",w=(e,t="sha512")=>t+"-"+Buffer.from(e,"hex").toString("base64"),k=e=>Buffer.from(e.split("-")[1],"base64").toString("hex"),I=2**32-1,P=(e,...t)=>{let n=0;const r=t.map((e=>{const t=Buffer.from(e);if(t.byteLength>I)throw Error(`NAR string too long: ${t.byteLength}`);return n+=8+8*Math.ceil(t.byteLength/8),t})),a=Buffer.alloc(n);let o=0;for(const e of r)a.writeUInt32LE(e.byteLength,o),e.copy(a,o+8),o+=8+8*Math.ceil(e.byteLength/8);e.write(a)},$=async(e,t,n)=>{if(t>I)throw Error(`NAR string too long: ${t}`);const r=Buffer.alloc(8);r.writeUInt32LE(t),e.write(r);for await(const t of n)e.write(t);const a=8-t%8;8!==a&&e.write(Buffer.alloc(a))},N=a.YarnVersion?.startsWith("3.")||!1,E={commands:[i,l,g],hooks:{afterAllInstalled:async(e,t)=>{!1!==t.persistProject&&e.configuration.get("enableNixify")&&await(async(e,t)=>{const{configuration:n,cwd:r}=e,{cache:o,report:i}=t,l=await s.xfs.realpathPromise(s.npath.toPortablePath((0,m.tmpdir)()));if(e.cwd.startsWith(l))return void i.reportInfo(0,`Skipping Nixify, because ${e.cwd} appears to be a temporary directory`);const d=n.get("nixExprPath"),f=n.get("yarnPath");let g;if(null===f){let e=(await s.xfs.readFilePromise(process.argv[1])).toString();if(e.startsWith("#!/nix/store/")){const t=e.substring(e.indexOf("\n")+1);e=`#!/usr/bin/env node\n${t}`}const t=a.hashUtils.makeHash(Buffer.from(e));g=["fetchurl {",` url = "https://repo.yarnpkg.com/${a.YarnVersion}/packages/yarnpkg-cli/bin/yarn.js";`,` hash = "${w(t)}";`,"}"].join("\n ")}else f.startsWith(r)?g="./"+s.ppath.relative(s.ppath.dirname(d),f):(g=p(f),i.reportWarning(0,`The Yarn path ${f} is outside the project - it may not be reachable by the Nix build`));const b=n.get("cacheFolder");let I;if(b.startsWith(r))I=p(s.ppath.relative(r,b));else{if(N||!n.get("enableGlobalCache"))throw Error(`The cache folder ${b} is outside the project, this is currently not supported`);I='".yarn/cache"'}const E=new Set;for(const e of n.sources.values())for(const t of e.split(", "))t.startsWith("<")||E.add(t);for(const e of E)s.ppath.resolve(r,e).startsWith(r)||i.reportWarning(0,`The config file ${e} is outside the project - it may not be reachable by the Nix build`);const D="./"+s.ppath.relative(s.ppath.dirname(d),s.ppath.resolve(r,"yarn.lock")),S=new Map,_=new Set(await s.xfs.readdirPromise(o.cwd)),L={unstablePackages:e.conditionalLocators};for(const t of e.storedPackages.values()){const{locatorHash:n}=t,r=e.storedChecksums.get(n),a=N?o.getLocatorPath(t,r||null,L):o.getLocatorPath(t,r||null);if(!a)continue;if(!_.has(s.ppath.basename(a)))continue;const i=r?o.getChecksumFilename(t,r):o.getVersionFilename(t);S.set(i,{pkg:t,checksum:r,cachePath:a})}const O=new Map,A=n.get("individualNixPackaging");let j="",T="";if(A){for(const[e,{pkg:t,checksum:n,cachePath:r}]of S.entries()){const o=a.structUtils.stringifyLocator(t),i=n?n.split("/").pop():await a.hashUtils.checksumFile(r);O.set(o,{cachePath:r,filename:e,hash:w(i)})}j="cacheEntries = {\n";for(const e of[...O.keys()].sort()){const t=O.get(e);j+=`${p(e)} = { ${[`filename = ${p(t.filename)};`,`hash = "${t.hash}";`].join(" ")} };\n`}j+="};"}else{const e=(0,c.createHash)("sha512");P(e,"nix-archive-1","(","type","directory");for(const t of[...S.keys()].sort()){const{cachePath:n}=S.get(t),{size:r}=await s.xfs.statPromise(n);P(e,"entry","(","name",t,"node","(","type","regular","contents"),await $(e,r,s.xfs.createReadStream(n)),P(e,")",")")}P(e,")"),e.end();for await(const t of e)T=w(t)}const C=n.get("isolatedNixBuilds");let R=new Set,F=[],B=[];const H=n.get("nodeLinker"),U=n.get("pnpUnpluggedFolder"),M=(t,n=new Set)=>{const r=a.structUtils.stringifyLocator(t);if(O.has(r)&&n.add(r),a.structUtils.isVirtualLocator(t)){const r=e.storedPackages.get(a.structUtils.devirtualizeLocator(t).locatorHash);if(!r)throw Error("Assertion failed: The locator should have been registered");M(r,n)}if(t.reference.startsWith("patch:")){const r=e.storedPackages.get(y.patchUtils.parseLocator(t).sourceLocator.locatorHash);if(!r)throw Error("Assertion failed: The locator should have been registered");M(r,n)}for(const r of t.dependencies.values()){const t=e.storedResolutions.get(r.descriptorHash);if(!t)throw Error("Assertion failed: The descriptor should have been registered");const a=e.storedPackages.get(t);if(!a)throw Error("Assertion failed: The locator should have been registered");M(a,n)}return n};for(const t of e.storedBuildState.keys()){const n=e.storedPackages.get(t);if(!n)throw Error("Assertion failed: The locator should have been registered");if(!C.includes(n.name))continue;let r;if("pnp"!==H)throw Error(`The nodeLinker ${H} is not supported for isolated Nix builds`);r=s.ppath.relative(e.cwd,s.ppath.join(U,a.structUtils.slugifyLocator(n),a.structUtils.getIdentVendorPath(n)));let o=n;if(a.structUtils.isVirtualLocator(o)){const{locatorHash:t}=a.structUtils.devirtualizeLocator(o),n=e.storedPackages.get(t);if(!n)throw Error("Assertion failed: The locator should have been registered");o=n}const i=a.structUtils.stringifyLocator(o),c=a.structUtils.stringifyLocator(n),l=`isolated.${p(i)}`;if(!R.has(o)){R.add(o);const e=[`pname = ${p(n.name)};`,`version = ${p(n.version)};`,`reference = ${p(o.reference)};`];if(A){const t=[...M(n)].sort().map((e=>`${p(e)}\n`)).join("");t&&e.push(`locators = [\n${t}];`)}const t=`override${V=n.name,V.split(/[^a-zA-Z0-9]+/g).filter((e=>e)).map((e=>{return(t=e).slice(0,1).toUpperCase()+t.slice(1);var t})).join("")}Attrs`;B.push(`${l} = optionalOverride (args.${t} or null) (mkIsolatedBuild { ${e.join(" ")} });`)}0===F.length&&F.push("# Copy in isolated builds."),F.push(`echo 'injecting build for ${n.name}'`,"yarn nixify inject-build \\",` ${p(c)} \\`,` \${${l}} \\`,` ${p(r)}`)}var V;if(F.length>0&&F.push("echo 'running yarn install'"),null==t.mode||0===C.length){const t=e.topLevelWorkspace.manifest.name,o=t?a.structUtils.stringifyIdent(t):"workspace",c=u("# This file is generated by running \"yarn install\" inside your project.\n# Manual changes might be lost - proceed with caution!\n\n{ lib, stdenv, nodejs, git, cacert, fetchurl, writeShellScript, writeShellScriptBin }:\n{ src, overrideAttrs ? null, ... } @ args:\n\nlet\n\n yarnBin = @@YARN_BIN@@;\n\n cacheFolder = @@CACHE_FOLDER@@;\n lockfile = @@LOCKFILE@@;\n\n # Call overrideAttrs on a derivation if a function is provided.\n optionalOverride = fn: drv:\n if fn == null then drv else drv.overrideAttrs fn;\n\n # Simple stub that provides the global yarn command.\n yarn = writeShellScriptBin \"yarn\" ''\n exec '${nodejs}/bin/node' '${yarnBin}' \"$@\"\n '';\n\n # Common attributes between Yarn derivations.\n drvCommon = {\n # Make sure the build uses the right Node.js version everywhere.\n buildInputs = [ nodejs yarn ];\n # All dependencies should already be cached.\n yarn_enable_network = \"0\";\n # Tell node-gyp to use the provided Node.js headers for native code builds.\n npm_config_nodedir = nodejs;\n };\n\n # Comman variables that we set in a Nix build, but not in a Nix shell.\n buildVars = ''\n # Make Yarn produce friendlier logging for automated builds.\n export CI=1\n # Tell node-pre-gyp to never fetch binaries / always build from source.\n export npm_config_build_from_source=true\n '';\n\n#@@ IF COMBINED_DRV\n cacheDrv = stdenv.mkDerivation {\n name = \"yarn-cache\";\n buildInputs = [ yarn git cacert ];\n buildCommand = ''\n cp --reflink=auto --recursive '${src}' ./src\n cd ./src/\n ${buildVars}\n HOME=\"$TMP\" yarn_enable_global_cache=false yarn_cache_folder=\"$out\" \\\n yarn nixify fetch\n rm $out/.gitignore\n '';\n outputHashMode = \"recursive\";\n outputHash = \"@@COMBINED_HASH@@\";\n };\n#@@ ENDIF COMBINED_DRV\n#@@ IF INDIVIDUAL_DRVS\n # Create derivations for fetching dependencies.\n cacheDrvs = let\n in lib.mapAttrs (locator: { filename, hash }: stdenv.mkDerivation {\n name = lib.strings.sanitizeDerivationName locator;\n buildInputs = [ yarn git cacert ];\n buildCommand = ''\n cd '${src}'\n ${buildVars}\n HOME=\"$TMP\" yarn_enable_global_cache=false yarn_cache_folder=\"$TMP\" \\\n yarn nixify fetch ${lib.escapeShellArg locator}\n # Because we change the cache dir, Yarn may generate a different name.\n mv \"$TMP/$(sed 's/-[^-]*\\.[^-]*$//' <<< \"$outputFilename\")\"-* $out\n '';\n outputFilename = filename;\n outputHash = hash;\n }) cacheEntries;\n\n # Create a shell snippet to copy dependencies from a list of derivations.\n mkCacheBuilderForDrvs = drvs:\n writeShellScript \"collect-yarn-cache\" (lib.concatMapStrings (drv: ''\n cp --reflink=auto ${drv} '${drv.outputFilename}'\n '') drvs);\n#@@ ENDIF INDIVIDUAL_DRVS\n\n#@@ IF NEED_ISOLATED_BUILD_SUPPRORT\n#@@ IF INDIVIDUAL_DRVS\n # Create a shell snippet to copy dependencies from a list of locators.\n mkCacheBuilderForLocators = let\n pickCacheDrvs = map (locator: cacheDrvs.${locator});\n in locators:\n mkCacheBuilderForDrvs (pickCacheDrvs locators);\n#@@ ENDIF INDIVIDUAL_DRVS\n\n # Create a derivation that builds a module in isolation.\n mkIsolatedBuild = { pname, version, reference, locators ? [] }: stdenv.mkDerivation (drvCommon // {\n inherit pname version;\n dontUnpack = true;\n\n configurePhase = ''\n ${buildVars}\n unset yarn_enable_nixify # plugin is not present\n '';\n\n buildPhase = ''\n mkdir -p .yarn/cache\n#@@ IF COMBINED_DRV\n cp --reflink=auto --recursive ${cacheDrv}/* .yarn/cache/\n#@@ ENDIF COMBINED_DRV\n#@@ IF INDIVIDUAL_DRVS\n pushd .yarn/cache > /dev/null\n source ${mkCacheBuilderForLocators locators}\n popd > /dev/null\n#@@ ENDIF INDIVIDUAL_DRVS\n\n echo '{ \"dependencies\": { \"${pname}\": \"${reference}\" } }' > package.json\n install -m 0600 ${lockfile} ./yarn.lock\n export yarn_global_folder=\"$TMP\"\n export yarn_enable_global_cache=false\n export yarn_enable_immutable_installs=false\n yarn\n '';\n\n installPhase = ''\n unplugged=( .yarn/unplugged/${pname}-*/node_modules/* )\n if [[ ! -e \"''${unplugged[@]}\" ]]; then\n echo >&2 \"Could not find the unplugged path for ${pname}\"\n exit 1\n fi\n\n mv \"$unplugged\" $out\n '';\n });\n#@@ ENDIF NEED_ISOLATED_BUILD_SUPPRORT\n\n # Main project derivation.\n project = stdenv.mkDerivation (drvCommon // {\n inherit src;\n name = @@PROJECT_NAME@@;\n\n configurePhase = ''\n ${buildVars}\n\n # Copy over the Yarn cache.\n rm -fr '${cacheFolder}'\n mkdir -p '${cacheFolder}'\n#@@ IF COMBINED_DRV\n cp --reflink=auto --recursive ${cacheDrv}/* '${cacheFolder}/'\n#@@ ENDIF COMBINED_DRV\n#@@ IF INDIVIDUAL_DRVS\n pushd '${cacheFolder}' > /dev/null\n source ${mkCacheBuilderForDrvs (lib.attrValues cacheDrvs)}\n popd > /dev/null\n#@@ ENDIF INDIVIDUAL_DRVS\n\n # Yarn may need a writable home directory.\n export yarn_global_folder=\"$TMP\"\n\n # Ensure global cache is disabled. Cache must be part of our output.\n touch .yarnrc.yml\n sed -i -e '/^enableGlobalCache/d' .yarnrc.yml\n echo 'enableGlobalCache: false' >> .yarnrc.yml\n\n # Some node-gyp calls may call out to npm, which could fail due to an\n # read-only home dir.\n export HOME=\"$TMP\"\n\n # running preConfigure after the cache is populated allows for\n # preConfigure to contain substituteInPlace for dependencies as well as the\n # main project. This is necessary for native bindings that maybe have\n # hardcoded values.\n runHook preConfigure\n\n@@ISOLATED_INTEGRATION@@\n\n # Run normal Yarn install to complete dependency installation.\n yarn install --immutable --immutable-cache\n\n runHook postConfigure\n '';\n\n buildPhase = ''\n runHook preBuild\n runHook postBuild\n '';\n\n installPhase = ''\n runHook preInstall\n\n # Move the package contents to the output directory.\n if grep -q '\"workspaces\"' package.json; then\n # We can't use `yarn pack` in a workspace setup, because it only\n # packages the outer workspace.\n mkdir -p \"$out/libexec\"\n mv $PWD \"$out/libexec/$name\"\n else\n # - If the package.json has a `files` field, only files matching those patterns are copied\n # - Otherwise all files are copied.\n yarn pack --out package.tgz\n mkdir -p \"$out/libexec/$name\"\n tar xzf package.tgz --directory \"$out/libexec/$name\" --strip-components=1\n\n cp --reflink=auto .yarnrc* \"$out/libexec/$name\"\n cp --reflink=auto ${lockfile} \"$out/libexec/$name/yarn.lock\"\n cp --reflink=auto --recursive .yarn \"$out/libexec/$name\"\n\n # Copy the Yarn linker output into the package.\n#@@ IF USES_PNP_LINKER\n cp --reflink=auto .pnp.* \"$out/libexec/$name\"\n#@@ ENDIF USES_PNP_LINKER\n#@@ IF USES_NM_LINKER\n cp --reflink=auto --recursive node_modules \"$out/libexec/$name\"\n#@@ ENDIF USES_NM_LINKER\n fi\n\n cd \"$out/libexec/$name\"\n\n # Invoke a plugin internal command to setup binaries.\n mkdir -p \"$out/bin\"\n yarn nixify install-bin $out/bin\n\n#@@ IF USES_NM_LINKER\n # A package with node_modules doesn't need the cache\n yarn cache clean\n#@@ ENDIF USES_NM_LINKER\n\n runHook postInstall\n '';\n\n passthru = {\n inherit nodejs;\n yarn-freestanding = yarn;\n yarn = writeShellScriptBin \"yarn\" ''\n exec '${yarn}/bin/yarn' --cwd '${overriddenProject}/libexec/${overriddenProject.name}' \"$@\"\n '';\n };\n });\n\n overriddenProject = optionalOverride overrideAttrs project;\n\n@@CACHE_ENTRIES@@\n@@ISOLATED@@\nin overriddenProject\n",{PROJECT_NAME:p(o),YARN_BIN:g,LOCKFILE:D,INDIVIDUAL_DRVS:A,COMBINED_DRV:!A,COMBINED_HASH:T,CACHE_FOLDER:I,CACHE_ENTRIES:j,ISOLATED:B.join("\n"),ISOLATED_INTEGRATION:h(" ",F.join("\n")),NEED_ISOLATED_BUILD_SUPPRORT:F.length>0,USES_PNP_LINKER:"pnp"===n.get("nodeLinker"),USES_NM_LINKER:"node-modules"===n.get("nodeLinker")}).replace(/\n\n\n+/g,"\n\n");if(await s.xfs.mkdirpPromise(s.ppath.dirname(d)),await s.xfs.writeFilePromise(d,c),n.get("generateDefaultNix")){const e=s.ppath.join(r,"default.nix"),t=s.ppath.join(r,"flake.nix");s.xfs.existsSync(e)||s.xfs.existsSync(t)||(await s.xfs.writeFilePromise(e,"# This is a minimal `default.nix` by yarn-plugin-nixify. You can customize it\n# as needed, it will not be overwritten by the plugin.\n\n{ pkgs ? import { } }:\n\npkgs.callPackage ./yarn-project.nix { } { src = ./.; }\n"),i.reportInfo(0,"A minimal default.nix was created. You may want to customize it."))}}n.get("enableNixPreload")&&s.xfs.existsSync(s.npath.toPortablePath("/nix/store"))&&await s.xfs.mktempPromise((async t=>{const n=["--add-fixed","sha512"],r=[];if(A)for(const[e,{cachePath:n,hash:a}]of O.entries()){const o=v(e),i=x(o,a);if(!s.xfs.existsSync(i)){const e=s.ppath.join(t,k(a).slice(0,7));await s.xfs.mkdirPromise(e);const i=s.ppath.join(e,o);await s.xfs.copyFilePromise(n,i),r.push(i)}}else{n.unshift("--recursive");const e=x("yarn-cache",T,{recursive:!0});if(!s.xfs.existsSync(e)){const e=s.ppath.join(t,"yarn-cache");await s.xfs.mkdirPromise(e);for(const[t,{cachePath:n}]of S.entries()){const r=s.ppath.join(e,t);await s.xfs.copyFilePromise(n,r)}r.push(e)}}try{const t=r.length;for(;0!==r.length;){const t=r.splice(0,100);await a.execUtils.execvp("nix-store",[...n,...t],{cwd:e.cwd,strict:!0})}0!==t&&i.reportInfo(0,A?`Preloaded ${t} packages into the Nix store`:"Preloaded cache into the Nix store")}catch(e){if("ENOENT"!==e.code)throw e}}))})(e,t)}},configuration:{enableNixify:{description:"If false, disables the Nixify plugin hook that generates Nix expressions",type:a.SettingsType.BOOLEAN,default:!0},nixExprPath:{description:"Path of the file where the project Nix expression will be written to",type:a.SettingsType.ABSOLUTE_PATH,default:"./yarn-project.nix"},generateDefaultNix:{description:"If true, a default.nix will be generated if it does not exist",type:a.SettingsType.BOOLEAN,default:!0},enableNixPreload:{description:"If true, cached packages will be preloaded into the Nix store",type:a.SettingsType.BOOLEAN,default:!0},individualNixPackaging:{description:"If true, generate one Nix derivation per package. If false, use a single derivation for the entire cache folder.",type:a.SettingsType.BOOLEAN,default:!1},isolatedNixBuilds:{description:"Dependencies with a build step that can be built in an isolated derivation",type:a.SettingsType.STRING,default:[],isArray:!0},installNixBinariesForDependencies:{description:"If true, the Nix output 'bin' directory will also contain executables for binaries defined by dependencies",type:a.SettingsType.BOOLEAN,default:!1}}};t=r})(),t}}; \ No newline at end of file diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..de9b346 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,24 @@ +enableScripts: true + +generateDefaultNix: false + +individualNixPackaging: true + +nodeLinker: node-modules + +supportedArchitectures: + os: + - current + - linux + cpu: + - current + - x64 + - arm64 + libc: + - current + - glibc + +plugins: + - checksum: 271de9a785321a37c564b8e2a6b5cd689850b20572d5fee8ec0a6b8e7a5d4cfb59e533524af2b4a537e00da12828abd062eaec6978556e3dd9149a020db2591b + path: .yarn/plugins/yarn-plugin-nixify.cjs + spec: "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/yarn-plugin-nixify-patched/raw/commit/4528fdd20b30d869262443b3f044549810e75fb8/dist/yarn-plugin-nixify.js" diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1ba2eb --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Studio graph capabilities + +This system package implements ordinary Canvas/Placement/Placeable interfaces. +It owns no database, web server, Worker, page session, or special presentation +object per rendered subject. The workspace binds storage and relationship edges. +The checked component is an independent consumer of the same public contracts. + +- `Canvas.create` creates an initialized canvas. The server entry factory uses the + workspace host's idempotent root protocol, without a second package-local cache. +- `Canvas.place` validates the requested renderer through the subject's portable + `Placeable.canPresent` contract and creates one placement with both relationships. +- `Canvas.createAndPlace` is an example compound factory: child canvas creation, + renderer validation and placement creation share one transaction. An invalid + renderer rolls back the child, too. Other domains can compose their own creation + ports in exactly this way; there is no frontend business reducer. +- `position` and `size` are separate whole-record registers. Concurrent move and + resize survive independently; concurrent moves resolve in server acceptance + order without mixing one move's x coordinate with another move's y coordinate. +- `remove` is explicitly server-only. Deletion needs the full incoming-reference + graph; a partial replica cannot safely predict all deletion restrictions and + cascades. Removing a placement preserves its subject. The interface does not + advertise a portable delete or keep a hidden archive in the canvas query. + +The generic `canPresent` implementation reads the explicitly granted +`reflection catalog [presentations]` port. This exposes only immutable checked +presentation IDs, subject contracts and their conforming atoms. It does not expose +live object inventory or private state. The dispatcher verifies the concrete +receiver; reference expectations supplied by a caller are not conformance proof. +A changed presentation catalog participates in the consuming execution group's +reuse digest. Unrelated modules do not acquire that dependency. + +Queries have explicit prototype bounds: 100 canvases in navigation and 200 +placements per open canvas. Title-only cards use their own small input projection. +A selected/new placement can retain `PlacementInputs` even if a list excludes it. +A larger Studio should add explicit windows or pagination rather than silently +pretend these bounded queries cover an arbitrarily large workspace. + +`tools/local-first/studio.nix` builds the source snapshot through the checked +artifact builder and tests the generic workspace host. Run +`nix build .#checks.x86_64-linux.camino-studio-capabilities` at the monorepo root. +These are unpublished fixture resource pins; normal independently retained source +assembly and scaffolding are qualified in WI-12. The old checkedServer/Yarn and +migration recipes were removed, rather than presenting them as a compatible path. diff --git a/components.nix b/components.nix new file mode 100644 index 0000000..c7de9de --- /dev/null +++ b/components.nix @@ -0,0 +1,6 @@ +{ + "component:studio:canvas-card" = { + entry = "src/canvas-card.tsx"; + export = "CanvasCard"; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ed908f5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,108 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "helpers": { + "flake": false, + "locked": { + "lastModified": 1793277056, + "narHash": "sha256-w7TrYSjU8hbVrqbLdHgxDJAzMdN2NoWe+HkInJLWRlI=", + "ref": "refs/tags/quixos-reachability/c2454054dac36fb7a6ff12004003170a65e4f288", + "rev": "c2454054dac36fb7a6ff12004003170a65e4f288", + "revCount": 85, + "type": "git", + "url": "https://repos.quixos.org/quixos-toolchain/quixos-nix-helpers.git" + }, + "original": { + "ref": "refs/tags/quixos-reachability/c2454054dac36fb7a6ff12004003170a65e4f288", + "rev": "c2454054dac36fb7a6ff12004003170a65e4f288", + "type": "git", + "url": "https://repos.quixos.org/quixos-toolchain/quixos-nix-helpers.git" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1783776592, + "narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "protocol": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1793284605, + "narHash": "sha256-FgV0Qhy9vxhjZhJs3lWu4NtYZ9FvvkUmM32bP2V73yA=", + "ref": "refs/tags/quixos-reachability/a025525f47cdff71f2496bb4e72cb9fc06b046ed", + "rev": "a025525f47cdff71f2496bb4e72cb9fc06b046ed", + "revCount": 235, + "type": "git", + "url": "https://repos.quixos.org/quixos-toolchain/quixos-protocol.git" + }, + "original": { + "ref": "refs/tags/quixos-reachability/a025525f47cdff71f2496bb4e72cb9fc06b046ed", + "rev": "a025525f47cdff71f2496bb4e72cb9fc06b046ed", + "type": "git", + "url": "https://repos.quixos.org/quixos-toolchain/quixos-protocol.git" + } + }, + "root": { + "inputs": { + "flake-utils": [ + "protocol", + "flake-utils" + ], + "helpers": "helpers", + "nixpkgs": [ + "protocol", + "nixpkgs" + ], + "protocol": "protocol" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c26eb50 --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + inputs.protocol.url = "git+https://repos.quixos.org/quixos-toolchain/quixos-protocol.git?ref=refs/tags/quixos-reachability/a025525f47cdff71f2496bb4e72cb9fc06b046ed&rev=a025525f47cdff71f2496bb4e72cb9fc06b046ed"; + inputs.nixpkgs.follows = "protocol/nixpkgs"; + inputs.flake-utils.follows = "protocol/flake-utils"; + inputs.helpers = { + url = "git+https://repos.quixos.org/quixos-toolchain/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/c2454054dac36fb7a6ff12004003170a65e4f288&rev=c2454054dac36fb7a6ff12004003170a65e4f288"; + flake = false; + }; + outputs = + { + nixpkgs, + flake-utils, + helpers, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + camino = import (helpers + "/quixos-package-helpers.nix"); + in + { + quixosPackages.checkedArtifacts = + context: + let + pkgs = camino.packageSet { inherit nixpkgs system context; }; + dependencies = + if builtins.pathExists ./yarn-project.nix then + camino.mkCaminoYarnDependencies { + inherit pkgs; + src = ./.; + } + else + null; + in + camino.assembleCaminoPackage { + inherit pkgs context; + artifacts = [ + (camino.mkCaminoTypeScriptPackage { + inherit pkgs context dependencies; + src = ./.; + entry = "src/server.ts"; + entries.browser = "src/browser.ts"; + }) + (camino.mkCaminoReactPackage { + inherit pkgs context dependencies; + src = ./.; + modules = import ./components.nix; + }) + ]; + }; + devShells.default = camino.mkCaminoYarnShell { inherit pkgs; }; + } + ); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2a06662 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "@quixos/web-studio-canvas-runtime", + "private": true, + "type": "module" +} diff --git a/package.qx b/package.qx new file mode 100644 index 0000000..bfc429a --- /dev/null +++ b/package.qx @@ -0,0 +1,45 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +import interface WebStudioFactory; +import interface WebStudioSearchable; +import interface StandaloneView; +import interface WebStudioCanvas; +import interface WebStudioCanvasPlacement; +import interface WebStudioPlaceable; +external atom WebStudioCanvas id "atom:org.quixos.web-studio:canvas"; +external atom WebStudioCanvasPlacement id "atom:org.quixos.web-studio:canvas-placement"; +type Position=record {x:double;y:double;}; +type Size=record {width:double;height:double;}; +type Layout=record {x:double;y:double;width:double;height:double;z:int32;}; +type PlacementSeed=record {canvas:interface-ref;subject:interface-ref;presentation:string;position:Position;size:Size;z:int32;}; +package WebStudioCanvasRuntime id "package:org.quixos.web-studio:canvas-runtime" revision "package:org.quixos.web-studio:canvas-runtime@1" { + function createPlaced id "studio:create-placed" : record {canvas:interface-ref;seed:Input;presentation:string;layout:Layout;} -> atom-ref execution transaction targets [JavaScript,server] requires { + interface factory id "factory" : WebStudioFactory using [create.call]; + interface canvas id "canvas" : WebStudioCanvas using [place.call] from input; + }; + function createCanvas id "studio:create-canvas" : string -> atom-ref execution transaction targets [JavaScript,server] requires {creation canvas id "canvas" : WebStudioCanvas input record {name:string;};}; + function createRoot id "studio:create-root" : unit -> atom-ref execution transaction targets [server] requires {creation canvas id "canvas" : WebStudioCanvas input record {name:string;};}; + operation canPresent id "studio:can-present" : string -> bool mode call execution snapshot targets [JavaScript,server] receiver object Subject requires {reflection catalog id "catalog" [presentations];}; + operation place id "studio:place" : record {subject:interface-ref;presentation:string;layout:Layout;} -> atom-ref mode call execution transaction targets [JavaScript,server] receiver any requires { + interface subject id "subject" : WebStudioPlaceable using [canPresent.call] from input; + creation placement id "placement" : WebStudioCanvasPlacement input PlacementSeed; + }; + operation createAndPlace id "studio:create-and-place" : record {name:string;presentation:string;layout:Layout;} -> atom-ref mode call execution transaction targets [JavaScript,server] receiver any requires { + interface subject id "subject" : WebStudioPlaceable using [canPresent.call] from input; + creation canvas id "canvas" : WebStudioCanvas input record {name:string;}; + creation placement id "placement" : WebStudioCanvasPlacement input PlacementSeed; + }; + module CanvasCard id "component:studio:canvas-card" implements StandaloneView.render; + query TitleInputs id "studio:title-inputs" root WebStudioCanvas document "queries/title-inputs.graphql" operation "TitleInputs" {replica;} + query TitleView id "studio:title-view" root WebStudioCanvas document "queries/title-view.graphql" operation "TitleView" {local from TitleInputs;watch;} + query CanvasIndexInputs id "studio:canvas-index-inputs" root workspace WebStudioCanvas document "queries/canvas-index-inputs.graphql" operation "CanvasIndexInputs" {replica;} + query CanvasIndex id "studio:canvas-index" root workspace WebStudioCanvas document "queries/canvas-index.graphql" operation "CanvasIndex" {local from CanvasIndexInputs;watch;} + query PlaceableInputs id "studio:placeable-inputs" root WebStudioPlaceable document "queries/placeable-inputs.graphql" operation "PlaceableInputs" {replica;} + query PlaceableView id "studio:placeable-view" root WebStudioPlaceable document "queries/placeable-view.graphql" operation "PlaceableView" {local from PlaceableInputs;watch;} + query PlacementInputs id "studio:placement-inputs" root WebStudioCanvasPlacement document "queries/placement-inputs.graphql" operation "PlacementInputs" {replica;} + query CanvasInputs id "studio:canvas-inputs" root WebStudioCanvas document "queries/canvas-inputs.graphql" operation "CanvasInputs" {replica;max rows 200;} + query CanvasView id "studio:canvas-view" root WebStudioCanvas document "queries/canvas-view.graphql" operation "CanvasView" {local from CanvasInputs;max rows 200;watch;} + query SearchInputs id "studio:search-inputs" root workspace WebStudioSearchable document "queries/search-inputs.graphql" operation "SearchInputs" {replica;} + query Search id "studio:search" root workspace WebStudioSearchable document "queries/search.graphql" operation "Search" {local from SearchInputs;watch;} + query LabelInputs id "studio:label-inputs" root workspace WebStudioSearchable document "queries/label-inputs.graphql" operation "LabelInputs" {replica;} + query Labels id "studio:labels" root workspace WebStudioSearchable document "queries/labels.graphql" operation "Labels" {local from LabelInputs;watch;} +} diff --git a/queries/canvas-index-inputs.graphql b/queries/canvas-index-inputs.graphql new file mode 100644 index 0000000..67aadef --- /dev/null +++ b/queries/canvas-index-inputs.graphql @@ -0,0 +1 @@ +query CanvasIndexInputs { root { objects(all: 100) { entries { node { _camino {ref} name } } } } } diff --git a/queries/canvas-index.graphql b/queries/canvas-index.graphql new file mode 100644 index 0000000..8c3dff4 --- /dev/null +++ b/queries/canvas-index.graphql @@ -0,0 +1 @@ +query CanvasIndex { root { availableObjects(all: 100) { entries { node { _camino {ref} name } } } } } diff --git a/queries/canvas-inputs.graphql b/queries/canvas-inputs.graphql new file mode 100644 index 0000000..f9945d4 --- /dev/null +++ b/queries/canvas-inputs.graphql @@ -0,0 +1 @@ +query CanvasInputs { root { name placements(all: 200) { entries { key node { _camino {ref} position {x y} size {width height} z presentation subject { _camino {ref} } } } } } } diff --git a/queries/canvas-view.graphql b/queries/canvas-view.graphql new file mode 100644 index 0000000..0a3b46d --- /dev/null +++ b/queries/canvas-view.graphql @@ -0,0 +1 @@ +query CanvasView { root { name placements(all: 200) { entries { key node { _camino {ref} position {x y} size {width height} z presentation subject { _camino {ref} } } } } } } diff --git a/queries/label-inputs.graphql b/queries/label-inputs.graphql new file mode 100644 index 0000000..20d3c65 --- /dev/null +++ b/queries/label-inputs.graphql @@ -0,0 +1 @@ +query LabelInputs($refs: [QxRef_WebStudioSearchable!]!) { root { objects(all: 100, where: {_camino: {ref: {in: $refs}}}) { entries { node { _camino {ref} title } } } } } diff --git a/queries/labels.graphql b/queries/labels.graphql new file mode 100644 index 0000000..4ccfe28 --- /dev/null +++ b/queries/labels.graphql @@ -0,0 +1 @@ +query Labels($refs: [QxRef_WebStudioSearchable!]!) { root { objects(all: 100, where: {_camino: {ref: {in: $refs}}}) { entries { node { _camino {ref} title } } } } } diff --git a/queries/placeable-inputs.graphql b/queries/placeable-inputs.graphql new file mode 100644 index 0000000..4d6d863 --- /dev/null +++ b/queries/placeable-inputs.graphql @@ -0,0 +1 @@ +query PlaceableInputs { root { _camino { ref } } } diff --git a/queries/placeable-view.graphql b/queries/placeable-view.graphql new file mode 100644 index 0000000..0e92da0 --- /dev/null +++ b/queries/placeable-view.graphql @@ -0,0 +1 @@ +query PlaceableView { root { _camino { ref } } } diff --git a/queries/placement-inputs.graphql b/queries/placement-inputs.graphql new file mode 100644 index 0000000..022b3ef --- /dev/null +++ b/queries/placement-inputs.graphql @@ -0,0 +1 @@ +query PlacementInputs { root { _camino {ref} position {x y} size {width height} z presentation subject { _camino {ref} } canvas { _camino {ref} name } } } diff --git a/queries/search-inputs.graphql b/queries/search-inputs.graphql new file mode 100644 index 0000000..cc63ef5 --- /dev/null +++ b/queries/search-inputs.graphql @@ -0,0 +1 @@ +query SearchInputs($text: String!, $after: Cursor) { root { objects(first: 30, after: $after, where: {or: [{title: {containsInsensitive: $text}}, {searchText: {containsInsensitive: $text}}]}, orderBy: {title: ASC}) { entries { node { _camino {ref} title } } pageInfo {hasNextPage endCursor} } } } diff --git a/queries/search.graphql b/queries/search.graphql new file mode 100644 index 0000000..392ff11 --- /dev/null +++ b/queries/search.graphql @@ -0,0 +1 @@ +query Search($text: String!, $after: Cursor) { root { objects(first: 30, after: $after, where: {or: [{title: {containsInsensitive: $text}}, {searchText: {containsInsensitive: $text}}]}, orderBy: {title: ASC}) { entries { node { _camino {ref} title } } pageInfo {hasNextPage endCursor} } } } diff --git a/queries/title-inputs.graphql b/queries/title-inputs.graphql new file mode 100644 index 0000000..fdc066b --- /dev/null +++ b/queries/title-inputs.graphql @@ -0,0 +1 @@ +query TitleInputs { root { name } } diff --git a/queries/title-view.graphql b/queries/title-view.graphql new file mode 100644 index 0000000..91b6bae --- /dev/null +++ b/queries/title-view.graphql @@ -0,0 +1 @@ +query TitleView { root { name } } diff --git a/quixos.check.json b/quixos.check.json new file mode 100644 index 0000000..528d4e1 --- /dev/null +++ b/quixos.check.json @@ -0,0 +1,4 @@ +{ + "backend": "execution", + "queryFrontends": ["react"] +} diff --git a/quixos.lock b/quixos.lock new file mode 100644 index 0000000..a3221a4 --- /dev/null +++ b/quixos.lock @@ -0,0 +1,9 @@ +quixos-lock version 1 { + quixos source { repository "https://gitea.internal.quixos.org/quixos/quixos.git"; commit "b1a88e33d6933cafbe89781effd1f75cc8fe6272"; } + interface WebStudioFactory source { repository "https://repos.quixos.org/quixos-studio/interface-web-studio-factory.git"; commit "a4136d9bfd765db6b2de2188ef2caf341e0f7a61"; } + interface WebStudioSearchable source { repository "https://repos.quixos.org/quixos-studio/interface-web-studio-searchable.git"; commit "b51116e6d320fc4dcd79f0d067124adf6a8e919f"; } + interface StandaloneView source { repository "https://repos.quixos.org/quixos-studio/interface-react-standalone-view.git"; commit "67b00f7ff8df87a477e12c5668c42684d8fa1708"; } + interface WebStudioCanvas source { repository "https://repos.quixos.org/quixos-studio/interface-web-studio-canvas.git"; commit "fb076e2d0d48080f76b88b2ffa4edeb4c37a7342"; } + interface WebStudioCanvasPlacement source { repository "https://repos.quixos.org/quixos-studio/interface-web-studio-canvas-placement.git"; commit "8bee874e37700fc860f066f11946efc82a1f521d"; } + interface WebStudioPlaceable source { repository "https://repos.quixos.org/quixos-studio/interface-web-studio-placeable.git"; commit "a998ebd982f7b61946156a0fa7038bc1295c3afc"; } +} diff --git a/quixos.react.json b/quixos.react.json new file mode 100644 index 0000000..b6fbb60 --- /dev/null +++ b/quixos.react.json @@ -0,0 +1,19 @@ +{ + "modules": { + "CanvasCard": { + "inputs": [ + { + "name": "body", + "query": "TitleView", + "root": { + "from": "input", + "path": ["subject"] + }, + "arguments": { + "record": {} + } + } + ] + } + } +} diff --git a/src/browser.ts b/src/browser.ts new file mode 100644 index 0000000..ad8d29e --- /dev/null +++ b/src/browser.ts @@ -0,0 +1,3 @@ +import { createRegistry } from "./gen/qx.js"; +import { handlers } from "./portable.js"; +export const registry = createRegistry(handlers); diff --git a/src/canvas-card.tsx b/src/canvas-card.tsx new file mode 100644 index 0000000..51252e0 --- /dev/null +++ b/src/canvas-card.tsx @@ -0,0 +1,2 @@ +import type { Modules } from "./gen/client.js"; +export const CanvasCard: Modules["CanvasCard"] = ({ input }) =>

{input.body.root.name}

; diff --git a/src/portable.ts b/src/portable.ts new file mode 100644 index 0000000..89e1e03 --- /dev/null +++ b/src/portable.ts @@ -0,0 +1,92 @@ +import { QxReject, type ExactReference, type MutationKey } from "@quixos/camino-replica-core"; +import { generic, type Contexts, type PortableImplementation } from "./gen/qx.js"; +const position = (value: { readonly x: number; readonly y: number }) => { + if (!Number.isFinite(value.x) || !Number.isFinite(value.y)) throw new QxReject("INVALID_POSITION"); +}; +const size = (value: { readonly width: number; readonly height: number }) => { + if (!Number.isFinite(value.width) || !Number.isFinite(value.height) || value.width <= 0 || value.height <= 0) + throw new QxReject("INVALID_SIZE"); +}; +const canPresent = async ({ + receiver, + input, + ports, +}: { + readonly receiver: ExactReference; + readonly input: string; + readonly ports: { + readonly catalog: { + presentations(): Promise>; + }; + }; +}) => { + const presentations = await ports.catalog.presentations(); + return presentations.some((p) => p.id === input && p.atoms.includes(receiver.expectation.id)); +}; +const place = async ({ receiver, input, ports }: Contexts["place"]) => { + position(input.layout); + size(input.layout); + if (!(await ports.subject["canPresent.call"]({ receiver: input.subject, input: input.presentation }))) + throw new QxReject("INVALID_PRESENTATION"); + return ports.placement.create( + { + canvas: { ...receiver, expectation: { kind: "interface", id: "interface:org.quixos.web-studio.canvas@1" } }, + subject: input.subject, + presentation: input.presentation, + position: { x: input.layout.x, y: input.layout.y }, + size: { width: input.layout.width, height: input.layout.height }, + z: input.layout.z, + }, + { key: "placement" }, + ); +}; +export const createPlaced = async ({ + input, + ports, +}: { + input: { + seed: Input; + canvas: Contexts["place"]["receiver"]; + presentation: string; + layout: Contexts["place"]["input"]["layout"]; + }; + ports: { + factory: { "create.call"(input: Input, key: MutationKey): Promise }; + canvas: { + "place.call"( + input: { receiver: ExactReference; input: Contexts["place"]["input"] }, + key: MutationKey, + ): Promise>>; + }; + }; +}) => { + const subject = await ports.factory["create.call"](input.seed, { key: "subject" }); + return ports.canvas["place.call"]( + { + receiver: input.canvas, + input: { + subject: { ...subject, expectation: { kind: "interface", id: "interface:org.quixos.web-studio.placeable@1" } }, + presentation: input.presentation, + layout: input.layout, + }, + }, + { key: "placement" }, + ); +}; +export const handlers: Omit = { + ...generic.canPresent(canPresent), + ...generic.createPlaced(createPlaced), + createCanvas: ({ input, ports }) => ports.canvas.create({ name: input }, { key: "canvas" }), + place, + createAndPlace: async (context) => { + const subject = await context.ports.canvas.create({ name: context.input.name }, { key: "canvas" }); + return place({ + ...context, + input: { + subject: { ...subject, expectation: { kind: "interface", id: "interface:org.quixos.web-studio.placeable@1" } }, + presentation: context.input.presentation, + layout: context.input.layout, + }, + }); + }, +}; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..960d8e1 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,7 @@ +import { createRegistry } from "./gen/qx.js"; +import { handlers } from "./portable.js"; +// The workspace entry protocol owns stable birth identity and idempotency. +export const registry = createRegistry({ + ...handlers, + createRoot: ({ ports }) => ports.canvas.create({ name: "Workspace" }, { key: "canvas" }), +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bcac4b4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2024", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "jsx": "react-jsx", + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +}